﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EFDataModel;
using EFDataModel.Enums;

namespace ContactMvc4TB.Helpers.DynamicDataType
{
    /// <summary>
    /// Modelbinder für unseren DynamicDataType, hier prüfen wir ob bei einem Postback unser Customtype mit übergeben wurde und wenn ja müssen wir für diesen die einzelnen Controls auswerten die erstellt wurden.
    /// und holt damit alle Eingegebenen Daten aus dem Postback und schreibt sie in unser Model
    /// http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System.Web/System/Web/Mvc/DefaultModelBinder.cs.htm
    ///
    /// ACHTUNG!!!! -> Damit der Binder auch aufgerufen wird, muss in der Web.Config das passende Model Registriert werden
    /// z.B.: ModelBinders.Binders.Add(typeof(UserAddModel), new DynamicDataModelBinder());
    /// </summary>
    public class DynamicDataModelBinder : DefaultModelBinder
    {
        /// <summary>
        /// Binden der Daten aus unserem Model mit den Daten die im request Form zurück gegeben werden.
        /// Außerdem wird überprüft ob es sich um valide Eingabewerte für den jeweiligen Eingabetyp handelt. Da die JavaScript Validation auch 
        /// ausgehebelt werden kann muss dies extra noch serverseitig passieren.
        /// </summary>
        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
        {
            //Wenn es sich um unsere Liste handelt, die gesetzt werden soll, dann diese extra behandeln
            if (propertyDescriptor.PropertyType == typeof(IEnumerable<EFDynamicData>))
            {
                //Initialisieren einer Liste
                List<EFDynamicData> dynamicDataList = new List<EFDynamicData>();

                //TODO Evtl. noch einbinden, das auch beim Erstellen eines Datensatzes darauf zugegriffen werden kann, aktuell nicht möglich, da die BaseId nicht existiert.
                //Dafür müsste dann evtl. das Modul mit in der URL stehen

                //Die aktuelle DatensatzId aus der URL ermitteln die gerade bearbeitet wird.
                int currentBaseId = int.Parse(controllerContext.RouteData.GetRequiredString(WebConstants.IdString));

                //Alle Eingabefelder heraussuchen, die zu unserem Dynamischen Eingabefeldern gehören
                var names = controllerContext.HttpContext.Request.Form.AllKeys.Where(x => x.StartsWith(EFDynamicDataType.DataTypeHtmlPrefix));
                DynamicDataValidation dynamicDataValidation = new DynamicDataValidation(controllerContext, bindingContext);

                //Es müssen alle controls für das Modul durchgegangen werden, denn wir müssen ja auch prüfen ob wirklich ein Wert zurück gegeben wurde
                //wenn es sich um einen Required Datentyp handelt bzw. bei Checkboxen müssen wir wissen ob diese auch "abgewählt wurde" und diese gibt bei einem Postback keine Daten zurück, wenn diese nicht ausgewählt wurde.
                //Mit hilfe der DatensatzID kann das Modul ermittelt werden an dem gerade gearbeitet wird, da jeder Datensatz einem Modul zugeordnet ist und dann wissen wir welche 
                //Anzeigefelder dargestellt werden sollen, die wir dann durchgehen und prüfen ob diese dementsprechend ausgefüllt wurden, außerdem wird anhand des Users
                //ermittelt welche Eingebefelder für diesen zur Verfügung stehen.
                foreach (EFDynamicDataType eDynamicDataType in EFDynamicDataType.GetVisibleDataTypesByBaseId(currentBaseId, CurrentSession.GetCurrentUser(), CurrentHttpContext.GetDataModel(), EViewType.Edit))
                {
                    dynamic currentValue;
                    //Prüfen um welchen Typ es sich handelt. da jeder anders ausgelesen und Interpretiert werden muss was die Eingabewerte darstellen
                    if (eDynamicDataType.DynamicDataType == EFDynamicDataTypes.Boolean)
                    {
                        //Prüfen ob im Postback die Daten mit übergeben wurden
                        if (names.Contains(eDynamicDataType.HtmlName))
                        {
                            //Wurde übergeben und die Checkbox ist damit ausgewählt
                            currentValue = true;
                        }
                        else
                        {
                            //Wurde nicht übergeben und die Checkbox ist damit nicht ausgewählt
                            currentValue = false;
                        }
                    }
                    else
                    {
                        //abhängig vom Datentyp muss aufgepast werden wie die Daten von der Oberfläche ausgelesen werden.
                        currentValue = HttpContext.Current.Request.Form[eDynamicDataType.HtmlName];
                    }

                    //Prüfen ob es sich um einen Validen Wert handelt der ausgelesen wurde für den aktuellen DynamicDataType und 
                    //nur wenn der Wert mit dem Eingabetyp übereinstimmt darf dieser unserer Liste hinzugefügt werden.
                    if (dynamicDataValidation.ValidateValue(currentValue, eDynamicDataType))
                    {
                        //Speichern der eingegebenen Daten in unserer dynamicDataList, welche dann dem Model hinzugefügt wird
                        AddDataToList(dynamicDataList, eDynamicDataType, currentValue, currentBaseId);
                    }
                }

                //Wenn ein Validierungsfehler bei den Postbackdaten dabei war, dann muss auch unserem Modelstate ein Fehler hinzugefügt werden,
                //damit automatisch im CurrentController Model.IsValid auch False ist und alles wie bisher verwendet werden kann.
                if (dynamicDataValidation.HasValidationErrors())
                {
                    //Einen Fehler dem Model hinzufügen damit wird automatisch das Model.IsValid = false!
                    bindingContext.ModelState.AddModelError("",GlobalResources.SystemMessages.InputErrorRESX);
                }

                //unser Custom PropertyBinding durchführen und die liste mit den Daten die eingegeben wurden mit an den controller übergeben
                SetProperty(controllerContext, bindingContext, propertyDescriptor, dynamicDataList);
            }
            else
            {
                //Standard Propertybinding durchführen
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }

        /// <summary>
        /// Hinzufügen der Dynamischen Daten in unsere Auflistung mit den Dynamischen Daten für den aktuellen Postback, diese Liste wird dann an
        /// das Model übergeben.
        /// </summary>
        /// <param name="dynamicDataList">Die Liste mit den Dynamischen Daten die gefüllt werden soll.</param>
        /// <param name="currentDataType">Der Aktuelle Datentyp um den es sich handelt</param>
        /// <param name="currentValue">der Wert der auf der Oberfläche eingegeben wurde und Valide ist!</param>
        /// <param name="baseId">Die Id zu dem der Datensatz gehört</param>
        private void AddDataToList(List<EFDynamicData> dynamicDataList, EFDynamicDataType currentDataType, dynamic currentValue, int baseId)
        {
            //Den Datentyp richtig aus der DB laden, da der übergebene CurrentDataType aus einer Statischen Liste kommt. Wenn der Datentyp
            //beim Erstellen nicht neu aus der DB geladen wird, dann werden die Daten nicht gespeichert, da diese nicht genau zugeorndet 
            EFDynamicDataType newDataType = CurrentHttpContext.GetDataModel().EFDynamicDataTypeMenge.Find(currentDataType.ID);

            //Prüfen ob überhaupt ein Eintrag angelegt werden soll. (boolean Werte lassen sich nicht auf IsNullOrEmpty testen!)
            //bzw. wir wollen nicht für jeden Leerstring der von jedem Textfeld zurück gegeben wird, in dem auch keine Werte stehen, wollen wir auch keinen extra Datensatz erstellen
            if (currentValue != null && (currentValue is Boolean || newDataType.CheckValueChanged(baseId, currentValue)))
            {
                EFDynamicData currentEntry;
                //Prüfen ob der Eintrag bereits angelegt wurde für den aktuellen Datensatz und wenn ja, dann muss der alte Wert überschrieben werden
                currentEntry = (from eDynamicData in CurrentHttpContext.GetDataModel().EFDynamicDataMenge
                                where eDynamicData.EFBaseID == baseId && eDynamicData.EFDynamicDataTypeID == currentDataType.ID
                                select eDynamicData).FirstOrDefault();

                //Einen neuen Eintrag anlegen, wenn dieser noch nicht existiert.
                if (currentEntry == null)
                {
                    currentEntry = new EFDynamicData();
                    currentEntry.EFDynamicDataTypeID = newDataType.ID;
                    currentEntry.EFDynamicDataType = newDataType;
                    currentEntry.EFBaseID = baseId;
                }

                //Wenn kein Wert hinterlegt wurden, dann kann der Eintrag direkt aus unserer DynamicData Auflistung entfernt werden.
                //Eine Ausnahme bildet hier der Boolean Wert, da wir nicht wissen wann dieser evtl. auf "False" gesetzt wurde, darf
                //ein Boolean Wert nicht aus der Liste entfernt werden, alle restlichen Datentypen werden aus der Liste entfernt wenn 
                //Ihnen kein Wert hinterlegt wurde.
                if (newDataType.DynamicDataType != EFDynamicDataTypes.Boolean && currentValue == string.Empty && currentEntry.ID > 0)
                {
                    //Es wurde kein Wert hinterlegt, also wird der Eintrag entfernt.
                    CurrentHttpContext.GetDataModel().EFDynamicDataMenge.Remove(currentEntry);
                }
                else
                {
                    //Setzten des passenden Datentypen, hier muss keine zusätzliche Prüfung vorgenommen werden, diese wurde bereits bei der Validation vorgenommen!
                    switch (newDataType.DynamicDataType)
                    {
                        //Es handelt sich um einen String, dann kann der Wert direkt ausgelesen werden
                        case EFDynamicDataTypes.String:
                        case EFDynamicDataTypes.EMail:
                        case EFDynamicDataTypes.Textblock:
                            currentEntry.StringValue = currentValue;
                            break;

                        //Der Datumswert soll abgespeichert werden
                        case EFDynamicDataTypes.Datetime:
                            currentEntry.DatetimeValue = DateTime.Parse(currentValue);
                            break;

                        //Der Ganzzahlenwert wird abgespeichert
                        case EFDynamicDataTypes.Integer:
                            currentEntry.IntegerValue = int.Parse(currentValue);
                            break;

                        //Der Warheitswert wird abgespeichert
                        case EFDynamicDataTypes.Boolean:
                            currentEntry.BooleanValue = currentValue;
                            break;

                        //Der Kommawert wird abgespeichert
                        case EFDynamicDataTypes.Decimal:
                            currentEntry.DecimalValue = double.Parse(currentValue);
                            break;

                        //Der Listenwert soll abgespeichert werden, dieser wird ebenfalls in der Integerspalte abgelegt
                        case EFDynamicDataTypes.List:
                            currentEntry.IntegerValue = int.Parse(currentValue);
                            break;
                    }

                    //Den Wert zu unserer Liste hinzufügen
                    dynamicDataList.Add(currentEntry);
                }
            }
        }
    }
}